/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.formserver.vmmanager.createvmmetadata;


import com.inspur.edp.bef.component.ICompParameter;
import com.inspur.edp.bef.component.ICompParameterCollection;
import com.inspur.edp.bef.component.base.GspComponent;
import com.inspur.edp.bef.component.base.ReturnValue;
import com.inspur.edp.bef.component.base.VoidReturnType;
import com.inspur.edp.bef.component.detailcmpentity.vm.VMComponent;
import com.inspur.edp.bef.component.detailcmpentity.vm.VMMethodParameter;
import com.inspur.edp.bef.component.enums.ParameterCollectionType;
import com.inspur.edp.bef.component.enums.ParameterMode;
import com.inspur.edp.bef.component.enums.ParameterType;
import com.inspur.edp.caf.db.dbaccess.DataValidator;
import com.inspur.edp.formserver.viewmodel.action.IViewModelParameter;
import com.inspur.edp.formserver.viewmodel.action.MappedCdpAction;
import com.inspur.edp.formserver.viewmodel.action.viewmodelbase.ViewModelParameter;
import com.inspur.edp.formserver.viewmodel.action.viewmodelbase.ViewModelVoidReturnValue;
import com.inspur.edp.formserver.viewmodel.exception.ViewModelException;
import com.inspur.edp.formserver.viewmodel.util.ViewModelUtils;
import com.inspur.edp.formserver.vmmanager.exception.VoManagerErrorCodes;
import com.inspur.edp.formserver.vmmanager.generatacmpcode.JavaCompCodeNames;
import com.inspur.edp.lcm.metadata.api.entity.GspMetadata;
import com.inspur.edp.lcm.metadata.api.entity.GspProject;
import com.inspur.edp.lcm.metadata.api.service.MetadataService;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.Iterator;

public class MappedCdpActionComponentGenerator {
    /**
     * 业务实体编号
     */
    protected String vmActionCode;
    /**
     * 业务实体程序集名称
     */
    protected String assemblyName;

    protected String namespace;
    /**
     * 业务对象ID
     */
    protected String bizObjectID;
    /**
     * 原始构件
     */
    protected VMComponent originalComponent;
    /**
     * 包前缀
     */
    private String packagePrefix;

    public static MappedCdpActionComponentGenerator getInstance() {
        return new MappedCdpActionComponentGenerator();
    }

    private MappedCdpActionComponentGenerator() {
    }

    /**
     * 生成构件元数据
     *
     * @param mappedCdpAction 业务操作
     * @param path            生成指定路径
     * @param vmActionCode    业务实体编号
     * @param assemblyName    业务实体程序集名称
     * @param bizObjectID     业务对象ID
     */
    public final String GenerateComponent(MappedCdpAction mappedCdpAction, String path, String vmActionCode, String assemblyName, String defaltnamespace, String bizObjectID) {
        this.vmActionCode = vmActionCode;
        this.assemblyName = assemblyName;
        namespace = defaltnamespace;
        this.bizObjectID = bizObjectID;
        //获取包路径前缀，common-model发版之后调用MetadataProjectUtil.getPackagePrefix(path);即可
        this.packagePrefix = getPackagePrefix(path);

        if (ViewModelUtils.checkNull(mappedCdpAction.getComponentEntityId())) {
            createComponent(mappedCdpAction, path);
            return mappedCdpAction.getCode();
        } else {
            modifyComponent(mappedCdpAction, path);
        }
        return null;
    }

    /**
     * 根据工程路径，获取包路径前缀，默认为com
     * @param relativePath
     * @return
     */
    private String getPackagePrefix(String relativePath) {
        DataValidator.checkForEmptyString(relativePath,"relativePath");
        GspProject gspProject = SpringBeanUtils.getBean(MetadataService.class).getGspProjectInfo(relativePath);
        DataValidator.checkForNullReference(gspProject, "gspProject");
        return gspProject.getPackagePrefix();
    }

    /**
     * 生成视图对象上的视图操作构件元数据，
     * @param mappedCdpAction
     * @param path
     * @param vmActionCode
     * @param assemblyName
     * @param defaltnamespace
     * @param bizObjectID
     * @return 返回新增的或者修改视图操作的编号
     */
    public final String generateVMComponent(MappedCdpAction mappedCdpAction, String path, String vmActionCode, String assemblyName, String defaltnamespace, String bizObjectID) {
        this.vmActionCode = vmActionCode;
        this.assemblyName = assemblyName;
        namespace = defaltnamespace;
        this.bizObjectID = bizObjectID;
        //获取包路径前缀，common-model发版之后调用MetadataProjectUtil.getPackagePrefix(path);即可
        this.packagePrefix = getPackagePrefix(path);
        if (ViewModelUtils.checkNull(mappedCdpAction.getComponentEntityId())) {
            createComponent(mappedCdpAction, path);
            return mappedCdpAction.getCode();
        } else {
            return modifyComponent(mappedCdpAction, path)? mappedCdpAction.getCode():null;
        }
    }
    /**
     * 新建构件
     *
     * @param action
     * @param path
     */
    private void createComponent(MappedCdpAction action, String path) {
        //1、构建实体
        VMComponent component = new VMComponent();
        this.originalComponent = null;
        //2、赋值
        evaluateComponentInfo(component, action, true);
        //3、生成构件
        String componentMetadataName = establishComponent(component, path);
        //4、建立Action与元数据之间的关联关系
        action.setComponentEntityId(component.getComponentID());
        //操作的ComponentName用来记录生成的构件元数据的名称
        action.setComponentName(componentMetadataName);
    }

    ///#region 构件赋值 isInit默认为true
    private void evaluateComponentInfo(VMComponent component, MappedCdpAction action, boolean isInit) {
        //1、基本信息
        evaluateComponentBasicInfo(component, action, isInit);
        //2、参数信息
        evaluateComponentParameterInfos(component, action);
        //3、返回值信息
        evaluateComponentReturnValueInfo(component, action);
    }

    private void evaluateComponentBasicInfo(VMComponent component, MappedCdpAction action, boolean isInit) {
        if (!ViewModelUtils.checkNull(action.getComponentEntityId())) {
            component.setComponentID(action.getComponentEntityId());
        }
        component.setComponentCode(action.getCode());
        component.setComponentName(action.getName());
        component.setComponentDescription("");  //无描述信息
        component.getVmMethod().setDotnetAssembly(this.assemblyName);

        String suffix = String.format("%1$s%2$s%3$s%4$s", '.', this.vmActionCode, '.', JavaCompCodeNames.VOActionNameSpaceSuffix);
        //添加前缀
        String packageName = javaModuleImportPackage(this.packagePrefix, namespace);
        //assembly对应JavaPackageName
        component.getMethod().setAssembly(packageName);
        packageName = String.format("%1$s%2$s", packageName, suffix.toLowerCase());

        if (this.originalComponent != null) {
            component.getVmMethod().setDotnetClassName(originalComponent.getMethod().getDotnetClassName());
            component.getMethod().setClassName(javaModuleClassName(originalComponent.getMethod().getDotnetClassName(), packageName));
        } else {
            //20190507wj_新生成的构件，VM=>VO
            if (isInit) {
                component.getVmMethod().setDotnetClassName((String.format("%1$s.%2$s.%3$s%4$s", this.namespace, JavaCompCodeNames.VMActionNameSpaceSuffix, action.getCode(), JavaCompCodeNames.VMActionClassNameSuffix)));
                component.getMethod().setClassName(javaModuleClassName(component.getVmMethod().getDotnetClassName(), packageName));
            }
        }
    }

    protected final String javaModuleClassName(String classNamestr, String packageNameStr) {
        String connections = "";
        if (!ViewModelUtils.checkNull(classNamestr)) {
            connections = classNamestr.substring(classNamestr.lastIndexOf('.'));
            connections = String.format("%1$s%2$s", packageNameStr, connections);
        }

        return (connections);
    }

    protected final String javaModuleImportPackage(String packagePrefix, String packageName) {
        String[] strArray = packageName.split("[.]", -1);
        //namespace前面添加包前缀
        String str = packagePrefix + ".";
        int i;

        for (i = 0; i < strArray.length - 1; i++) {
            str += strArray[i].toLowerCase() + ".";
        }
        return (str + strArray[i].toLowerCase());
    }

    private void evaluateComponentParameterInfos(VMComponent component, MappedCdpAction action) {
        for (Object parameter : action.getParameterCollection()) {
            evaluateComponentParameterInfo((IViewModelParameter) parameter, component);
        }
    }

    private void evaluateComponentParameterInfo(IViewModelParameter vmParameter, VMComponent component) {
        VMMethodParameter tempVar = new VMMethodParameter();
        tempVar.setID(vmParameter.getID());
        tempVar.setParamCode(vmParameter.getParamCode());
        tempVar.setParamName(vmParameter.getParamName());
        tempVar.setParamDescription(vmParameter.getParamDescription());
        tempVar.setMode(ParameterMode.valueOf(vmParameter.getMode().name().toString()));
        tempVar.setParameterType(ParameterType.valueOf(vmParameter.getParameterType().name().toString()));
        tempVar.setAssembly(vmParameter.getAssembly());
        tempVar.setDotnetClassName(((ViewModelParameter) vmParameter).getDotnetClassName());
        tempVar.setClassName(vmParameter.getClassName());
        tempVar.setParameterCollectionType(ParameterCollectionType.valueOf(vmParameter.getCollectionParameterType().name().toString()));
        VMMethodParameter parameter = tempVar;

        component.getVmMethod().getParams().add(parameter);
    }

    private void evaluateComponentReturnValueInfo(VMComponent component, MappedCdpAction action) {
        //类型
        if (action.getReturnValue() instanceof ViewModelVoidReturnValue) {
            component.getVmMethod().setReturnValue(new VoidReturnType());
        } else {
            component.getVmMethod().getReturnValue().setParameterCollectionType(ParameterCollectionType.valueOf(action.getReturnValue().getCollectionParameterType().name().toString()));
            component.getVmMethod().getReturnValue().setParameterType(ParameterType.valueOf(action.getReturnValue().getParameterType().name().toString()));
            component.getVmMethod().getReturnValue().setAssembly(action.getReturnValue().getAssembly());
            component.getVmMethod().getReturnValue().setDotnetClassName(action.getReturnValue().getDotnetClassName());
            component.getVmMethod().getReturnValue().setClassName(action.getReturnValue().getClassName());
        }
        //基本信息
        component.getVmMethod().getReturnValue().setID(action.getReturnValue().getID());
        component.getVmMethod().getReturnValue().setParamDescription(action.getReturnValue().getParamDescription());
    }
    ///#endregion

    /**
     * 生成构件实体对应的构件元数据
     *
     * @param component 构件实体
     * @param path      生成构件元数据指定路径
     * @return 生成的构件元数据名称
     * <see cref="string"/>
     */
    private String establishComponent(GspComponent component, String path) {
        return GspMetadataExchangeUtil.getInstance().establishVMMetdadata(component, path, this.vmActionCode, this.bizObjectID, this.namespace);
    }

    /**
     * 修改构件
     *
     * @param action
     * @param path
     */
    private boolean modifyComponent(MappedCdpAction action, String path) {
        //1、构建实体
        VMComponent component = new VMComponent();

        //3、获取要修改的构件元数据完整路径（包括后缀）
        String fullPath = "";
        MetadataService metadataService = SpringBeanUtils.getBean(MetadataService.class);
        // 带针对不同类型构件的扩展名的文件全名
        String metadataFileNameWithExtendName = action.getComponentName() + ".vmCmp";
        if (!metadataService.isMetadataExist(path, metadataFileNameWithExtendName)) {
            throw new ViewModelException(VoManagerErrorCodes.GSP_VIEWOBJECT_MANAGER_0009, null, path, metadataFileNameWithExtendName);
        }
        fullPath = path + ViewModelUtils.getSeparator() + action.getComponentName() + ".vmCmp";
        this.originalComponent = getoriginalComponent(path, metadataFileNameWithExtendName);
        //2、赋值
        evaluateComponentInfo(component, action, true);
        //4、修改更新构件元数据
        GspMetadataExchangeUtil.getInstance().updateGspMetadata(component, fullPath, this.vmActionCode, this.bizObjectID, this.namespace);
        return this.isComponentMethodChanged(originalComponent,component);
    }
    private boolean isComponentMethodChanged(GspComponent originalComponent, GspComponent component) {
        if (originalComponent != null && originalComponent.getCompMethod() != null) {
            if (component != null && component.getCompMethod() != null) {
                return this.isComponentMethodReturnTypeChanged(originalComponent.getCompMethod().getReturnValue(), component.getCompMethod().getReturnValue()) || this.isComponentMethodParameterCollectionChanged(originalComponent.getCompMethod().getCompParameterCollection(), component.getCompMethod().getCompParameterCollection());
            } else {
                return false;
            }
        } else {
            return true;
        }
    }

    private boolean isComponentMethodReturnTypeChanged(ReturnValue originalReturnValue, ReturnValue returnValue) {
        if (originalReturnValue == returnValue) {
            return false;
        } else if (originalReturnValue != null && returnValue != null) {
            return originalReturnValue.getClass() != returnValue.getClass() || originalReturnValue.getParameterCollectionType() != returnValue.getParameterCollectionType() || originalReturnValue.getParameterTypeEnum() != returnValue.getParameterTypeEnum() || !StringUtils.equals(originalReturnValue.getAssembly(), returnValue.getAssembly()) || !StringUtils.equals(originalReturnValue.getClassName(), returnValue.getClassName()) || !StringUtils.equals(originalReturnValue.getDotnetClassName(), returnValue.getDotnetClassName());
        } else {
            return true;
        }
    }

    private boolean isComponentMethodParameterCollectionChanged(ICompParameterCollection<ICompParameter> originalParameters, ICompParameterCollection<ICompParameter> parameters) {
        if (originalParameters == parameters) {
            return false;
        } else if (originalParameters != null && parameters != null && originalParameters.getCount() == parameters.getCount()) {
            Iterator<ICompParameter> originalIter = originalParameters.iterator();
            Iterator iterator = parameters.iterator();

            while(true) {
                if (originalIter.hasNext() && iterator.hasNext()) {
                    ICompParameter originalParam = (ICompParameter)originalIter.next();
                    ICompParameter parameter = (ICompParameter)iterator.next();
                    if (originalParam == parameter) {
                        continue;
                    }

                    if (originalParam != null && parameter != null) {
                        if (StringUtils.equals(originalParam.getParamCode(), parameter.getParamCode()) && StringUtils.equals(originalParam.getParamName(), parameter.getParamName()) && originalParam.getParameterCollectionType() == parameter.getParameterCollectionType() && originalParam.getParameterTypeEnum() == parameter.getParameterTypeEnum() && StringUtils.equals(originalParam.getClassName(), parameter.getClassName())) {
                            continue;
                        }

                        return true;
                    }

                    return true;
                }

                return false;
            }
        } else {
            return true;
        }
    }

    private VMComponent getoriginalComponent(String path, String metadataFileNameWithSuffix) {
        MetadataService metadataService = SpringBeanUtils.getBean(MetadataService.class);
        GspMetadata metadata = metadataService.loadMetadata(metadataFileNameWithSuffix, path);
        VMComponent originalComponent = (VMComponent) ((metadata.getContent() instanceof VMComponent) ? metadata.getContent() : null);
        return originalComponent;
    }
}